#[macro_use]
mod events;
pub mod data;
pub mod gfx;

events_macro! {
    keyboard: {
        key_escape: Escape,
        key_up: Up,
        key_down: Down,
        key_left: Left,
        key_right: Right,
        key_space: Space
    },
    else: {
        quit: Quit { .. }
    }
}

use crate::phi::gfx::Sprite;
use crate::views;
use sdl2::pixels::Color;
use sdl2::render::Renderer;
use sdl2::ttf::Sdl2TtfContext;
use sdl2::Sdl;
use std::collections::HashMap;
use std::path::Path;

pub struct Phi<'window> {
  pub events: Events,
  pub canvas: Renderer<'window>,
  pub ttf_context: &'window Sdl2TtfContext,
  cached_fonts: HashMap<(&'static str, u16), sdl2::ttf::Font<'window, 'window>>,
}

impl<'window> Phi<'window> {
  fn new(
    events: Events,
    canvas: Renderer<'window>,
    ttf_context: &'window Sdl2TtfContext,
  ) -> Phi<'window> {
    Phi {
      events,
      canvas,
      ttf_context,
      cached_fonts: HashMap::new(),
    }
  }

  pub fn output_size(&self) -> (f64, f64) {
    let (w, h) = self.canvas.output_size().unwrap();
    (w as f64, h as f64)
  }

  pub fn ttf_str_sprite(
    &mut self,
    text: &str,
    font_path: &'static str,
    size: u16,
    color: Color,
  ) -> Option<Sprite> {
    if let Some(font) = self.cached_fonts.get(&(font_path, size)) {
      return font
        .render(text)
        .blended(color)
        .ok()
        .and_then(|surface| {
          self.canvas.create_texture_from_surface(&surface).ok()
        })
        .map(Sprite::new);
    }
    self
      .ttf_context
      .load_font(Path::new(font_path), size)
      .ok()
      .and_then(|font| {
        self.cached_fonts.insert((font_path, size), font);
        self.ttf_str_sprite(text, font_path, size, color)
      })
  }
}

pub trait View {
  fn render(&mut self, context: &mut Phi, elapsed: f64) -> ViewAction;
}

pub enum ViewAction {
  None,
  Quit,
  ChangeView(Box<dyn View>),
}

pub fn spawn(title: &str) {
  let sdl2_context: Sdl = sdl2::init().unwrap();
  let video = sdl2_context.video().unwrap();
  let mut timer = sdl2_context.timer().unwrap();

  let window = video
    .window(title, 800, 600)
    .position_centered()
    .opengl()
    .resizable()
    .build()
    .unwrap();

  let canvas = window.renderer().accelerated().build().unwrap();
  let events = Events::new(sdl2_context.event_pump().unwrap());

  let ttf_context = sdl2::ttf::init().unwrap();

  let mut context: Phi = Phi::new(events, canvas, &ttf_context);
  let mut current_view: Box<dyn View> =
    Box::new(views::menu::Menu::new(&mut context));

  let interval = 1000 / 60;
  let mut before = timer.ticks();
  'running: loop {
    let now = timer.ticks();
    let dt = now - before;
    let elapsed = dt as f64 / 1000.0;
    if dt < interval {
      timer.delay(interval - dt);
      continue;
    }
    before = now;
    context.events.pump(&mut context.canvas);
    match current_view.render(&mut context, elapsed) {
      ViewAction::None => context.canvas.present(),
      ViewAction::Quit => break 'running,
      ViewAction::ChangeView(view) => current_view = view,
    }
  }
}
